import { Injectable } from "@angular/core";
import { Device } from "@ionic-native/device";
import { ConfigurationStoreService } from "./configuration-store.service";
import { CryptoService } from "./crypto.service";
import { ValueTransformer } from "@angular/compiler/src/util";

/**
 * 配置项读写服务类
 *
 * (需要在AppModule中全局注入)
 *
 * @export
 * @class ConfigurationService
 */
@Injectable()
export class ConfigurationService {

    private cache: Map<string, ConfigurationContainer>;

    constructor(private store: ConfigurationStoreService, private crypto: CryptoService, private device: Device) {
        this.cache = new Map<string, ConfigurationContainer>();
    }

    public get profile(): Profile {
        const key = "profile";
        if (!this.cache.has(key)) {
            this.cache.set(key, new Profile(this.store));
        }
        return this.cache.get(key) as Profile;
    }

    public get personal(): PersonalSecurity {
        const key = "personal";
        const storedValue = this.system.currentUser.value;

        let currentUser: User;
        if (typeof storedValue === "undefined" || storedValue === null || storedValue.length === 0) {
            currentUser = { id: this.crypto.md5.digest("anonymous"), name: "anonymous" };
        }
        else {
            currentUser = JSON.parse(storedValue) as User;
        }
        if (!this.cache.has(key)) {
            this.cache.set(key, new PersonalSecurity(this.store, currentUser, this.crypto, this.device));
        }
        else {
            let cached = this.cache.get(key) as PersonalSecurity;
            if (cached.currentUser !== currentUser.name) {
                this.cache.set(key, new PersonalSecurity(this.store, currentUser, this.crypto, this.device));
            }
        }
        return this.cache.get(key) as PersonalSecurity;
    }

    public get system(): SystemSecurity {
        const key = "system";
        if (!this.cache.has(key)) {
            this.cache.set(key, new SystemSecurity(this.store));
        }
        return this.cache.get(key) as SystemSecurity;
    }

    public clear() {
        this.cache.forEach(element => {
            element.clear();
        });
        this.cache.clear();
    }
}

class ConfigurationKeys {
    // Profile
    // static agentLevel = "agent_level";
    // static contacts = "contacts";
    // static hasSubordinate = "has_subordinate";
    // static language = "lang";
    // static mapClients = "map_clients";
    // static nickName = "nick_name";
    // static profileUrl = "profile_url";
    // static roles = "roles";
    // static tokenExpireUpdated = "token_expire_updated";
    // static userName = "user_name";
    static agentLevel = "agentLevel";
    static contacts = "contacts";
    static hasSubordinate = "hasSubordinate";
    static language = "lang";
    static mapClients = "map_clients";
    static nickName = "nickName";
    static profileUrl = "profileURL";
    static roles = "roles";
    static tokenExpireUpdated = "tokenExpireUpdated";
    static userName = "userName";
    // Token
    static accessToken = "access_token";
    static refreshToken = "refresh_token";
    // Setting
    static isFingerprintEnabled = "is_fingerprint_enabled"; // 指纹登录是否启用
    static isGraphicEnabled = "is_graphic_enabled";         // 手势登录是否启用
    static unlockGraph = "unlock_graph"                     // 解锁图形保存
    // Status
    static passwordErrorTimes = "password_error_times";     // 用户名密码登录已错误次数
    static graphicErrorTimes = "graphic_error_times";       // 手势登录已错误次数
    static lastAccessed = "last_accessed";                  // 上次访问时间

    // System
    static currentUser = "current_user";                    // 当前/上一次登录的用户名
    static deviceUsers = "device_users";                    // 记录设备已登录过的用户
    static isLockedOut = "is_locked_out";                   // 用户手动登出

    static security = "_security";                          // 安全相关配置集合
}

export interface User {
    id: string;
    name: string;
}

export interface ConfigurationItem {
    remove(): void;
    value: string;
}

class ScatteredConfigurationItem implements ConfigurationItem {

    private cache: string;

    constructor(private key: string, private store: ConfigurationStoreService, private cipher: boolean = false) {
    }

    get value(): string {
        return this.get();
    }

    set value(value: string) {
        this.set(value);
    }

    remove() {
        this.store.removeItem(this.key);
        delete this.cache;
    }

    protected get(): string {
        if (typeof this.cache === "undefined") {
            this.cache = this.store.getItem(this.key, this.cipher);
        }
        return this.cache;
    }

    protected set(value: string) {
        this.cache = value;
        this.store.setItem(this.key, value, this.cipher);
    }
}

class PackedConfigurationItem implements ConfigurationItem {

    private cache: string;

    constructor(private key: string, private store: ConfigurationStoreService, private propName: string) {
    }

    get value(): string {
        return this.get();
    }

    set value(value: string) {
        this.set(value);
    }

    remove() {
        delete this.cache;

        const data = this.store.getItem(this.key);
        const obj = data !== null && data !== "" ? JSON.parse(data) : {};
        delete obj[this.propName];
        this.store.setItem(this.key, JSON.stringify(obj));
    }

    protected get(): string {
        if (typeof this.cache === "undefined") {
            const data = this.store.getItem(this.key);
            const obj = data !== null && data !== "" ? JSON.parse(data) : {};
            this.cache = obj[this.propName];
        }
        return this.cache;
    }

    protected set(value: string) {
        this.cache = value;

        const data = this.store.getItem(this.key);
        const obj = data !== null && data !== "" ? JSON.parse(data) : {};
        obj[this.propName] = value;
        this.store.setItem(this.key, JSON.stringify(obj));
    }
}

export class UnlockGraph extends PackedConfigurationItem {
    constructor(key: string, store: ConfigurationStoreService, propName: string, private device: Device, private crypto: CryptoService) {
        super(key, store, propName)
    }

    get value(): string {
        return this.get();
    }

    set value(value: string) {
        const hashed = this.hash(value);
        this.set(hashed);
    }

    equal(value: string): boolean {
        const hashed = this.hash(value);
        return this.get() === hashed;
    }

    protected hash(value: string): string {
        const salted = this.device.uuid + value;
        return this.crypto.md5.digest(salted);
    }
}

abstract class ConfigurationContainer {

    protected cache: Map<string, ConfigurationItem>;

    constructor(protected store: ConfigurationStoreService) {
        this.cache = new Map<string, ConfigurationItem>();
    }

    public clear() {
        this.cache.forEach(element => {
            element.remove();
        });
        this.cache.clear();
    }

    protected readCache(key: string, creator?: (key: string, store: ConfigurationStoreService) => ConfigurationItem): ConfigurationItem {
        if (!this.cache.has(key)) {
            const item = typeof creator === "undefined" ? this.createItem(key, this.store) : creator(key, this.store);
            this.cache.set(key, item);
        }
        return this.cache.get(key);
    }

    protected abstract createItem(key: string, store: ConfigurationStoreService): ConfigurationItem;
}

class ScatteredConfigurationContainer extends ConfigurationContainer {
    protected createItem(key: string, store: ConfigurationStoreService): ConfigurationItem {
        return new ScatteredConfigurationItem(key, store);
    }
}

class PackedConfigurationContainer extends ConfigurationContainer {
    constructor(store: ConfigurationStoreService, private storeKey: string) {
        super(store);
    }

    public clear() {
        this.store.removeItem(this.storeKey);
        this.cache.clear();
    }

    protected readCache(key: string, creator?: (key: string, store: ConfigurationStoreService) => ConfigurationItem): ConfigurationItem {
        if (!this.cache.has(key)) {
            const item = typeof creator === "undefined" ? this.createItem(key, this.store) : creator(key, this.store);
            this.cache.set(key, item);
        }
        return this.cache.get(key);
    }

    protected createItem(key: string, store: ConfigurationStoreService): ConfigurationItem {
        return new PackedConfigurationItem(this.storeKey, store, key);
    }
}

export class Profile extends ScatteredConfigurationContainer {

    constructor(store: ConfigurationStoreService) {
        super(store);
    }

    public get agentLevel(): ConfigurationItem {
        return this.readCache(ConfigurationKeys.agentLevel);
    }

    public get contacts(): ConfigurationItem {
        return this.readCache(ConfigurationKeys.contacts);
    }

    public get hasSubordinate(): ConfigurationItem {
        return this.readCache(ConfigurationKeys.hasSubordinate);
    }

    public get language(): ConfigurationItem {
        return this.readCache(ConfigurationKeys.language);
    }

    public get mapClients(): ConfigurationItem {
        return this.readCache(ConfigurationKeys.mapClients);
    }

    public get nickName(): ConfigurationItem {
        return this.readCache(ConfigurationKeys.nickName);
    }

    public get profileUrl(): ConfigurationItem {
        return this.readCache(ConfigurationKeys.profileUrl);
    }

    public get roles(): ConfigurationItem {
        return this.readCache(ConfigurationKeys.roles);
    }

    public get tokenExpireUpdated(): ConfigurationItem {
        return this.readCache(ConfigurationKeys.tokenExpireUpdated);
    }

    public get userName(): ConfigurationItem {
        return this.readCache(ConfigurationKeys.userName);
    }
}

export class PersonalSecurity extends PackedConfigurationContainer {

    constructor(store: ConfigurationStoreService, private user: User, private crypto: CryptoService, private device: Device) {
        super(store, ConfigurationKeys.security + ":" + user.id);
    }

    public get currentUser(): string {
        return this.user.name;
    }

    public get passwordErrorTimes(): ConfigurationItem {
        return this.readCache(ConfigurationKeys.passwordErrorTimes);
    }

    public get graphicErrorTimes(): ConfigurationItem {
        return this.readCache(ConfigurationKeys.graphicErrorTimes);
    }

    public get lastAccessed(): ConfigurationItem {
        return this.readCache(ConfigurationKeys.lastAccessed);
    }

    public get isLockedOut(): ConfigurationItem {
        return this.readCache(ConfigurationKeys.isLockedOut);
    }

    public get accessToken(): ConfigurationItem {
        return this.readCache(ConfigurationKeys.accessToken);
    }

    public get refreshToken(): ConfigurationItem {
        return this.readCache(ConfigurationKeys.refreshToken);
    }

    public get isFingerprintEnabled(): ConfigurationItem {
        return this.readCache(ConfigurationKeys.isFingerprintEnabled);
    }

    public get isGraphicEnabled(): ConfigurationItem {
        return this.readCache(ConfigurationKeys.isGraphicEnabled);
    }

    public get unlockGraph(): ConfigurationItem {
        return this.readCache(ConfigurationKeys.unlockGraph, (key, store) =>
            new UnlockGraph(key, store, ConfigurationKeys.security, this.device, this.crypto)
        );
    }
}

export class SystemSecurity extends PackedConfigurationContainer {

    constructor(store: ConfigurationStoreService) {
        super(store, ConfigurationKeys.security);
    }

    public get isLockedOut(): ConfigurationItem {
        return this.readCache(ConfigurationKeys.isLockedOut);
    }

    public get currentUser(): ConfigurationItem {
        return this.readCache(ConfigurationKeys.currentUser);
    }

    public get deviceUsers(): ConfigurationItem {
        return this.readCache(ConfigurationKeys.deviceUsers);
    }
}